<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layout :: page(~{::body})}">
<head>
    <link rel="stylesheet" th:href="@{/css/app.css?v=20251015e}" href="/css/app.css?v=20251015e">
</head>
<body>
<div class="dash-toolbar" id="dashToolbar">
    <div id="deviceCount"></div>
    <div class="grow"></div>
    <button id="autoBtn" type="button" class="btn-icon auto-refresh" onclick="toggleAuto()" title="开启自动刷新" aria-label="开启自动刷新" aria-pressed="false">
        <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4v3.6m0-3.6 2.8 2.8M12 4 9.2 6.8M6 12a6 6 0 1 0 6-6" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke="currentColor" stroke-width="1.8"/></svg>
        <span class="visually-hidden">开启自动刷新</span>
    </button>
    <div id="editModeSwitch" class="edit-switch" role="switch" aria-checked="false" tabindex="0" title="开启编辑模式" aria-label="开启编辑模式" onclick="toggleEditMode()" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();toggleEditMode();}">
        <div class="track"><div class="thumb"></div></div>
        <span id="editModeSr" class="visually-hidden">开启编辑模式</span>
    </div>
    <span id="autoStatus" class="auto-indicator" style="display:none;" aria-hidden="true">
        <span class="dot" aria-hidden="true"></span>
        <span class="label">AUTO</span>
    </span>
    <span id="autoLive" class="visually-hidden" aria-live="polite"></span>
    <span id="lastRefresh" class="text-dim" style="font-size:.6rem;min-width:90px;text-align:right;"></span>
</div>

<div th:if="${#lists.isEmpty(devices)}" class="card" id="noDeviceCard" style="max-width:520px;margin:0 auto;">
    <p class="text-dim" style="margin:.2rem 0 .6rem;font-size:.75rem;">暂无设备。使用右下角 “＋” 添加。</p>
</div>

<div class="grid" th:if="${!#lists.isEmpty(devices)}" id="deviceGridInitial">
    <div class="card" th:each="dev : ${devices}" th:attr="data-device-id=${dev.deviceId},data-device-name=${dev.deviceName},data-device-protocol=${dev.protocol},data-device-conn=${dev.connectionString}">
        <div class="dev-actions">
            <button type="button" class="delete-x btn-icon danger edit-only" title="删除设备" aria-label="删除设备" onclick="deleteDevice(this.closest('.card').dataset.deviceId,this);event.stopPropagation();">
                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 7h12M10 7V5h4v2m-7 0v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V7M9 11v6M15 11v6" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <span class="visually-hidden">删除设备</span>
            </button>
        </div>
        <div class="device-title" style="display:flex;align-items:center;gap:.4rem;margin:0 0 .1rem;">
            <h3 th:text="${dev.deviceName}" style="margin:0;padding-right:2.2rem;">设备</h3>
            <button type="button" class="btn-icon edit-only" title="编辑设备" aria-label="编辑设备" th:onclick="|openEditDevice(${dev.deviceId});event.stopPropagation();|">
                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 17.5V20h2.5L17.81 8.69a1 1 0 0 0 0-1.41L15.72 5.19a1 1 0 0 0-1.41 0L5.5 14.5M13.5 6.5l2 2" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <span class="visually-hidden">编辑设备</span>
            </button>
        </div>
        <p class="text-dim" style="margin:0.3rem 0;font-size:0.7rem;letter-spacing:.5px;">
            协议: <span th:text="${dev.protocol}"></span>
            | 连接: <strong th:text="${dev.connectionOk} ? '正常' : '失败'" th:style="${dev.connectionOk} ? 'color:var(--color-ok);' : 'color:var(--color-danger);'"></strong>
        </p>
        <div class="inline-actions" style="margin:0.3rem 0;">
            <button type="button" class="btn-icon ok edit-only" title="添加点位" aria-label="添加点位" onclick="openNamespaces(this.closest('.card').dataset.deviceId);event.stopPropagation();">
                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5v14M5 12h14" stroke-linecap="round"/></svg>
                <span class="visually-hidden">添加点位</span>
            </button>
            <button type="button" class="btn-icon edit-only" title="编辑点位" aria-label="编辑点位" onclick="openEditTags(this.closest('.card').dataset.deviceId);event.stopPropagation();">
                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 17.5V20h2.5L17.81 8.69a1 1 0 0 0 0-1.41L15.72 5.19a1 1 0 0 0-1.41 0L5.5 14.5M13.5 6.5l2 2" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <span class="visually-hidden">编辑点位</span>
            </button>
        </div>
        <table class="table tags-table" style="font-size:0.72rem;">
            <thead>
            <tr>
                <th style="width:40%;position:relative;padding-bottom:28px;">
                    Tag
                    <button type="button" class="filter-btn edit-only" th:id="${'filterBtn-name-' + dev.deviceId}" th:onclick="|openFilterPop(${dev.deviceId},'name');event.stopPropagation();|" aria-label="过滤名称">
                        <svg viewBox="0 0 24 24"><path d="M4 5h16L14 13v6l-4-2v-4z" stroke-linecap="round" stroke-linejoin="round"/></svg>
                    </button>
                    <div class="filter-pop" th:attr="data-device=${dev.deviceId}" th:id="${'filterPop-name-' + dev.deviceId}" data-open="0">
                        <input type="text" placeholder="包含..." />
                        <div class="fp-actions">
                            <button type="button" class="fp-apply btn-icon" th:onclick="|applyFilter(${dev.deviceId},'name')|" title="确定" aria-label="确定">
                                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 13l4 4L19 7" stroke-linecap="round" stroke-linejoin="round"/></svg>
                                <span class="visually-hidden">确定</span>
                            </button>
                            <button type="button" class="fp-clear btn-icon" th:onclick="|clearFilter(${dev.deviceId},'name')|" title="清除" aria-label="清除">
                                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6l-12 12" stroke-linecap="round" stroke-linejoin="round"/></svg>
                                <span class="visually-hidden">清除</span>
                            </button>
                        </div>
                    </div>
                </th>
                <th style="width:30%;position:relative;padding-bottom:28px;">
                    地址
                    <button type="button" class="filter-btn edit-only" th:id="${'filterBtn-address-' + dev.deviceId}" th:onclick="|openFilterPop(${dev.deviceId},'address');event.stopPropagation();|" aria-label="过滤地址">
                        <svg viewBox="0 0 24 24"><path d="M4 5h16L14 13v6l-4-2v-4z" stroke-linecap="round" stroke-linejoin="round"/></svg>
                    </button>
                    <div class="filter-pop" th:attr="data-device=${dev.deviceId}" th:id="${'filterPop-address-' + dev.deviceId}" data-open="0">
                        <input type="text" placeholder="包含..." />
                        <div class="fp-actions">
                            <button type="button" class="fp-apply btn-icon" th:onclick="|applyFilter(${dev.deviceId},'address')|" title="确定" aria-label="确定">
                                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 13l4 4L19 7" stroke-linecap="round" stroke-linejoin="round"/></svg>
                                <span class="visually-hidden">确定</span>
                            </button>
                            <button type="button" class="fp-clear btn-icon" th:onclick="|clearFilter(${dev.deviceId},'address')|" title="清除" aria-label="清除">
                                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6l-12 12" stroke-linecap="round" stroke-linejoin="round"/></svg>
                                <span class="visually-hidden">清除</span>
                            </button>
                        </div>
                    </div>
                </th>
                <th style="width:30%;position:relative;padding-bottom:28px;">
                    值
                    <button type="button" class="filter-btn edit-only" th:id="${'filterBtn-value-' + dev.deviceId}" th:onclick="|openFilterPop(${dev.deviceId},'value');event.stopPropagation();|" aria-label="过滤数值">
                        <svg viewBox="0 0 24 24"><path d="M4 5h16L14 13v6l-4-2v-4z" stroke-linecap="round" stroke-linejoin="round"/></svg>
                    </button>
                    <div class="filter-pop" th:attr="data-device=${dev.deviceId}" th:id="${'filterPop-value-' + dev.deviceId}" data-open="0">
                        <input type="text" placeholder="包含..." />
                        <div class="fp-actions">
                            <button type="button" class="fp-apply btn-icon" th:onclick="|applyFilter(${dev.deviceId},'value')|" title="确定" aria-label="确定">
                                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 13l4 4L19 7" stroke-linecap="round" stroke-linejoin="round"/></svg>
                                <span class="visually-hidden">确定</span>
                            </button>
                            <button type="button" class="fp-clear btn-icon" th:onclick="|clearFilter(${dev.deviceId},'value')|" title="清除" aria-label="清除">
                                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6l-12 12" stroke-linecap="round" stroke-linejoin="round"/></svg>
                                <span class="visually-hidden">清除</span>
                            </button>
                        </div>
                    </div>
                </th>
            </tr>
            </thead>
            <tbody th:id="${'tagBody-' + dev.deviceId}">
            <tr th:each="tag,stat : ${dev.tags}" th:if="${stat.index < 10}" class="tag-row" th:attr="data-device-id=${dev.deviceId},data-tag-id=${tag.id},data-tag-name=${tag.name}" onclick="event.stopPropagation();">
                <td th:text="${tag.name}"></td>
                <td th:text="${tag.address}"></td>
                <td style="font-family:var(--font-mono);" th:text="${tag.value}"></td>
            </tr>
            <tr th:if="${#lists.isEmpty(dev.tags)}"><td colspan="3" class="text-dim" style="padding:.35rem .3rem;">暂无点位</td></tr>
            <tr th:if="${!#lists.isEmpty(dev.tags) and #lists.size(dev.tags) > 10}"><td colspan="3" class="text-dim" style="padding:.35rem .3rem;font-size:.6rem;">其余点位将在加载后可翻页查看...</td></tr>
            </tbody>
        </table>
        <div class="tag-pagination" th:id="${'tagPager-' + dev.deviceId}" style="display:none;">
            <button type="button" class="btn-icon pager-prev" aria-label="上一页" th:onclick="|changeTagPage(${dev.deviceId},-1)|">
                <svg viewBox="0 0 24 24"><path d="M14 6l-6 6 6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
            </button>
            <span class="pager-info">1 / 1</span>
            <button type="button" class="btn-icon pager-next" aria-label="下一页" th:onclick="|changeTagPage(${dev.deviceId},1)|">
                <svg viewBox="0 0 24 24"><path d="M10 6l6 6-6 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
            </button>
        </div>
    </div>
</div>

<div id="drawerOverlay" class="drawer-overlay" aria-hidden="true">
    <div class="drawer" role="complementary" aria-label="历史与预测面板">
        <div id="drawerResizer" class="drawer-resizer" role="separator" aria-orientation="vertical" aria-label="调整宽度" tabindex="0"></div>
        <div class="drawer-header">
            <h3 id="drawerTitle">历史趋势</h3>
            <span id="drawerUpdateTime"></span>
            <button class="btn-icon drawer-close" type="button" title="关闭" aria-label="关闭" onclick="closeDrawer()">
                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6l-12 12" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <span class="visually-hidden">关闭</span>
            </button>
        </div>
        <div class="drawer-body">
            <div>
                <div class="drawer-section-title">折线图</div>
                <div class="chart-shell">
                    <canvas id="drawerCanvas" style="width:100%;height:240px;display:block;"></canvas>
                    <div id="drawerTooltip" class="chart-tooltip" style="display:none;"></div>
                    <div id="chartCrosshairV" class="chart-crosshair-v" style="display:none;"></div>
                </div>
                <div id="chartLegend" class="legend">
                    <div data-ser="hist">
                        <span class="lg-box"></span>
                        <span>历史</span>
                    </div>
                    <div data-ser="pred" id="legendPred" role="button" tabindex="0" aria-pressed="true" aria-label="切换预测曲线显示" style="display:none;">
                        <span class="lg-box lg-box-forecast"></span>
                        <span>预测</span>
                    </div>
                </div>
                <div id="compareInfo" class="compare-info" style="display:none;"></div>
                <div id="drawerHint" class="hint" style="display:none;">当前数据非数值型或为空，显示下方表格。</div>
                <div class="pred-btn-row">
                    <button id="predictBtn" type="button" class="btn-icon" title="生成 / 刷新预测 (叠加显示)" aria-label="生成预测" onclick="doPredictInDrawer()">
                        <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 15l4-5 4 6 4-8 4 6" stroke-linecap="round" stroke-linejoin="round"/></svg>
                        <span class="visually-hidden">预测</span>
                    </button>
                    <span id="predictStatus" class="text-dim" style="display:none;font-size:.65rem;">预测中...</span>
                </div>
                <div id="metricBox" class="metrics" style="display:none;"></div>
                <div id="compareTableOuter">
                    <div id="compareCollapseHeader" role="button" tabindex="0" aria-expanded="false" aria-controls="compareTableWrap" onclick="toggleCompareCollapse()" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();toggleCompareCollapse();}">
                        <div class="drawer-section-title">历史 vs 预测 对比</div>
                        <div>
                            <span id="compareCount"></span>
                            <span id="compareCollapseIcon">▼</span>
                        </div>
                    </div>
                    <div id="compareTableWrap" style="display:none;">
                        <div style="max-height:180px;overflow:auto;border:1px solid var(--color-border);border-radius:6px;">
                            <table class="small-table">
                                <thead><tr><th>预测时间</th><th>预测值</th><th>历史时间</th><th>历史值</th><th>差值</th><th>差异%</th></tr></thead>
                                <tbody id="compareTBody"><tr><td colspan="6" class="text-dim">暂无数据</td></tr></tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </div>
            <div>
                <div class="drawer-section-title">最新数据 (倒序)</div>
                <div style="max-height:240px;overflow:auto;border:1px solid var(--color-border);border-radius:6px;">
                    <table class="small-table">
                        <thead><tr><th style="width:55%;">时间</th><th style="width:45%;">值</th></tr></thead>
                        <tbody id="drawerTableBody"><tr><td colspan="2" class="text-dim">加载中...</td></tr></tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</div>

<div id="addDeviceModal" class="modal-overlay" aria-hidden="true" role="dialog" aria-modal="true">
    <div class="modal-wrapper">
        <div class="modal-header">
            <h3>添加设备</h3>
            <button class="btn-icon close-x" type="button" title="关闭" aria-label="关闭" onclick="closeAddDeviceModal()">
                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6l-12 12" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <span class="visually-hidden">关闭</span>
            </button>
        </div>
        <form id="addDeviceForm" onsubmit="return submitAddDevice()">
            <div class="form-row">
                <label for="devName">设备名称</label>
                <input id="devName" type="text" placeholder="例如：Boiler-01" required maxlength="120">
            </div>
            <div class="form-row">
                <label for="devProtocol">通信协议</label>
                <select id="devProtocol" required><option value="opcua">OPC UA</option></select>
            </div>
            <div class="form-row">
                <label for="devConn">连接字符串</label>
                <input id="devConn" type="text" placeholder="opc.tcp://host:port" required maxlength="500">
            </div>
            <div id="addDevMsg" class="text-dim"></div>
            <div class="modal-actions">
                <button type="button" class="btn-icon" title="取消" aria-label="取消" onclick="closeAddDeviceModal()">
                    <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6l-12 12" stroke-linecap="round" stroke-linejoin="round"/></svg>
                    <span class="visually-hidden">取消</span>
                </button>
                <button type="submit" class="btn-icon ok" title="保存" aria-label="保存">
                    <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 13l4 4L19 7" stroke-linecap="round" stroke-linejoin="round"/></svg>
                    <span class="visually-hidden">保存</span>
                </button>
            </div>
        </form>
    </div>
</div>

<div id="editDeviceModal" class="modal-overlay" aria-hidden="true" role="dialog" aria-modal="true" style="display:none;">
    <div class="modal-wrapper">
        <div class="modal-header">
            <h3>编辑设备</h3>
            <button class="btn-icon close-x" type="button" title="关闭" aria-label="关闭" onclick="closeEditDeviceModal()">
                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6l-12 12" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <span class="visually-hidden">关闭</span>
            </button>
        </div>
        <form id="editDeviceForm" onsubmit="return submitEditDevice()">
            <input type="hidden" id="editDevId">
            <div class="form-row">
                <label for="editDevName">设备名称</label>
                <input id="editDevName" type="text" required maxlength="120">
            </div>
            <div class="form-row">
                <label for="editDevProtocol">通信协议</label>
                <select id="editDevProtocol" required><option value="opcua">OPC UA</option></select>
            </div>
            <div class="form-row">
                <label for="editDevConn">连接字符串</label>
                <input id="editDevConn" type="text" required maxlength="500">
            </div>
            <div id="editDevMsg" class="text-dim"></div>
            <div class="modal-actions">
                <button type="button" class="btn-icon" title="取消" aria-label="取消" onclick="closeEditDeviceModal()">
                    <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6l-12 12" stroke-linecap="round" stroke-linejoin="round"/></svg>
                    <span class="visually-hidden">取消</span>
                </button>
                <button type="submit" class="btn-icon ok" title="保存" aria-label="保存">
                    <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 13l4 4L19 7" stroke-linecap="round" stroke-linejoin="round"/></svg>
                    <span class="visually-hidden">保存</span>
                </button>
            </div>
        </form>
    </div>
</div>

<div id="namespaceModal" class="modal-overlay" aria-hidden="true" role="dialog" aria-modal="true" style="display:none;">
    <div class="ns-wrapper">
        <div class="ns-header">
            <h2>添加点位</h2>
            <div class="flex gap-sm">
                <span id="nsSelectedLabel" class="text-dim">未选择命名空间</span>
                <button type="button" class="btn-icon close-btn" title="关闭" aria-label="关闭" onclick="closeNsModal()">
                    <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6l-12 12" stroke-linecap="round" stroke-linejoin="round"/></svg>
                    <span class="visually-hidden">关闭</span>
                </button>
            </div>
        </div>
        <div class="ns-columns">
            <div class="ns-left">
                <div class="flex">
                    <div class="drawer-section-title">Namespaces</div>
                    <div id="nsListStatus" class="text-dim"></div>
                </div>
                <ul id="nsList">
                    <li class="text-dim">加载中...</li>
                </ul>
            </div>
            <div class="ns-right">
                <div class="ns-tags-head">
                    <div class="drawer-section-title">Tags</div>
                    <div id="nsStatus" class="ns-status"></div>
                </div>
                <div class="ns-tags-container">
                    <table>
                        <thead><tr><th>名称</th><th>地址</th><th>值</th><th>操作</th></tr></thead>
                        <tbody id="nsTagBody"><tr><td colspan="4" class="text-dim">左侧选择命名空间</td></tr></tbody>
                    </table>
                </div>
            </div>
        </div>
        <div class="modal-actions">
            <button type="button" class="btn-icon close-btn" onclick="closeNsModal()" title="关闭" aria-label="关闭">
                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6l-12 12" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <span class="visually-hidden">关闭</span>
            </button>
        </div>
    </div>
</div>

<div id="editTagsModal" class="modal-overlay" aria-hidden="true" role="dialog" aria-modal="true" style="display:none;">
    <div class="modal-wrapper">
        <div class="modal-header">
            <h3>编辑点位</h3>
            <button class="btn-icon close-x" type="button" title="关闭" aria-label="关闭" onclick="closeEditTagsModal()">
                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6l-12 12" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <span class="visually-hidden">关闭</span>
            </button>
        </div>
        <div style="max-height:60vh;overflow:auto;">
            <table class="table small-table">
                <thead><tr><th>名称</th><th>地址</th><th>操作</th></tr></thead>
                <tbody id="editTagsBody"><tr><td colspan="3" class="text-dim">加载中...</td></tr></tbody>
            </table>
        </div>
        <div id="editTagsMsg" class="text-dim"></div>
        <div class="modal-actions">
            <button type="button" class="btn-icon close-btn" title="关闭" aria-label="关闭" onclick="closeEditTagsModal()">
                <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6l-12 12" stroke-linecap="round" stroke-linejoin="round"/></svg>
                <span class="visually-hidden">关闭</span>
            </button>
        </div>
    </div>
</div>

<div class="fab-container" id="fab">
    <button class="fab-main" id="fabMain" type="button" aria-label="添加设备" onclick="openAddDeviceModal()">＋</button>
</div>

<script th:src="@{/js/data-dashboard.js?v=20251015e}" src="/js/data-dashboard.js?v=20251015e" defer></script>
</body>
</html>